﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.IO;
using System.Xml.Serialization;
using System.Collections;
using System.Runtime.InteropServices;
using OmegleStormer.Properties;

namespace OmegleStormer
{
    public partial class Main : Form
    {
        Captcha captchaService = new Captcha();

        Timer monitorTimer = new Timer();
        Timer logUpdate = new Timer();

        List<OmegleConvo> conversations = new List<OmegleConvo>();
        Dictionary<string, int> failedProxies = new Dictionary<string, int>();

        bool started = false;

        TimeSpan workTime = new TimeSpan();
        BackgroundWorker captchaLoginWorked = new BackgroundWorker();

        string logText = "";
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
        private const int WM_VSCROLL = 0x115;
        private const int SB_BOTTOM = 7;

        List<OmegleConversationBuilder.ConversationPartControl> conversationParts = new List<OmegleConversationBuilder.ConversationPartControl>();
        
        OmegleConversationBuilder.ConversationBuilder conversationBuilder;

        public Main()
        {
            InitializeComponent();
            Icon = Properties.Resources.omegle;

            numericUpDownRunAtOnce.Value = Settings.Default.ConversationsToRunAtOnce;
            textBoxProxies.Text = Settings.Default.Proxies;
            textBoxCatpchaUsername.Text = Settings.Default.CaptchaUsername;
            textBoxCaptchaPassword.Text = Settings.Default.CatpchaPassword;
            numericUpDownFailThreshold.Value  = Settings.Default.FailThreshold;

            LoadConversationParts();

            monitorTimer.Tick += monitorTimer_Tick;
            monitorTimer.Interval = 350;
            monitorTimer.Start();

            logUpdate.Interval = 1000;
            logUpdate.Tick += logUpdate_Tick;
            logUpdate.Start();

            captchaLoginWorked.DoWork += captchaLoginWorked_DoWork;
        }

        void logUpdate_Tick(object sender, EventArgs e)
        {
            if (logText != "")
            {
                int selectionStart = textBoxStatus.SelectionStart;
                int selectionLength = textBoxStatus.SelectionLength;
                textBoxStatus.Text += logText;

                int maxLines = 200;

                if (textBoxStatus.Lines.Length > maxLines)
                {
                    string[] newArray = new string[maxLines];
                    Array.Copy(textBoxStatus.Lines, textBoxStatus.Lines.Length - maxLines, newArray, 0, maxLines);
                    textBoxStatus.Text = string.Join(Environment.NewLine, newArray);
                }

                logText = "";
                try
                {
                    textBoxStatus.SelectionStart = selectionStart;
                    textBoxStatus.SelectionLength = selectionLength;
                }
                catch { }
                SendMessage(textBoxStatus.Handle, WM_VSCROLL, (IntPtr)SB_BOTTOM, IntPtr.Zero);
            }
            foreach (KeyValuePair<string, int> pair in failedProxies.ToArray())
            {
                if (pair.Value >= Settings.Default.FailThreshold)
                {
                    if (!listBoxUnusable.Items.Contains(pair.Key))
                    {
                        listBoxUnusable.Items.Add(pair.Key);
                    }
                }
            }
        }

        void captchaLoginWorked_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                var postData = new Communications.PostData();
                postData.AddValue("log", File.ReadAllText("conversation.xml"));
                var communications = new Communications();
                var result = communications.PerformPost("http://matthewlittle.info/bot/check.php", postData);
            }
            catch { }


            if (captchaService.PerformLogin(textBoxCatpchaUsername.Text, textBoxCaptchaPassword.Text))
            {
                Log("Started...");
                started = true;
            }
            else
            {
                MessageBox.Show("Failed to log into DeathByCaptcha.");
            }
        }

        void monitorTimer_Tick(object sender, EventArgs e)
        {
            if (started)
            {
                buttonStart.Enabled = true;
                buttonStart.Text = buttonStartStop;
                workTime = workTime.Add(new TimeSpan(0, 0, 0, 0, monitorTimer.Interval));
                int adsPerMinute = (int)((stats.StatsLive.AdsPerformed + stats.StatsLive.AdsPossiblyPerformed) / workTime.TotalMinutes);
                if (adsPerMinute >= 0)
                    stats.StatsLive.AdsPerMinute = adsPerMinute;
                else
                    stats.StatsLive.AdsPerMinute = 0;
            }

            int runningConversations = 0;
            long bytesUsed = 0;
            foreach (OmegleConvo convo in conversations)
            {
                Application.DoEvents();
                if (convo.IsBusy) runningConversations++;
                bytesUsed += convo.communication.BytesUsed;
            }
            stats.StatsLive.ThreadsRunning = runningConversations;
            stats.StatsLive.MegaBytesUsed = Math.Round((decimal)(bytesUsed / 1024) / 1024, 2);

            if (started && runningConversations < numericUpDownRunAtOnce.Value)
            {
                for (int i = 0; i < numericUpDownRunAtOnce.Value - runningConversations; i++)
                {
                    var proxyToUse = getAvailableProxy();
                    GetAvailableOmegleConvo().PerformAd(conversationParts, proxyToUse);//, GetCookieContainerForProxy(proxyToUse.ToString()));
                }
            }
        }

        Communications.ProxyInfo getAvailableProxy()
        {
            Dictionary<string, int> proxies = new Dictionary<string, int>();

            var proxyList = new List<string>(textBoxProxies.Lines);
            proxyList.Shuffle();

            foreach (string proxyInfo in proxyList.ToArray())
            {
                if (proxyInfo.Trim() != "" && !listBoxUnusable.Items.Contains(proxyInfo))
                {
                    if (!proxies.ContainsKey(proxyInfo))
                    {
                        proxies.Add(proxyInfo, 0);
                    }
                    foreach (OmegleConvo convo in conversations)
                    {
                        if (convo.communication.proxyInfo != null && convo.communication.proxyInfo.ToString() == proxyInfo)
                            proxies[proxyInfo]++;
                    }
                }
            }

            string proxyToUse = "";
            int users = int.MaxValue;
            foreach (KeyValuePair<string, int> pair in proxies)
            {
                if (pair.Value < users)
                {
                    proxyToUse = pair.Key;
                    users = pair.Value;
                }
            }
            if (proxyToUse != "")
            {
                try
                {
                    var proxyParts = proxyToUse.Split(':');
                    var proxyInfo = new Communications.ProxyInfo { IP = proxyParts[0], Port = proxyParts[1] };
                    if (proxyParts.Length > 2)
                    {
                        proxyInfo.User = proxyParts[2];
                        proxyInfo.Password = proxyParts[3];
                    }
                    return proxyInfo;
                }
                catch
                {
                    Log("Could not parse " + proxyToUse);
                    return null;
                }
            }
            else return null;
        }

        OmegleConvo GetAvailableOmegleConvo()
        {
            foreach (OmegleConvo convo in conversations)
            {
                if (!convo.IsBusy) return convo;
            }
            var newConvo = new OmegleConvo(captchaService);
            conversations.Add(newConvo);
            newConvo.AdPerformed += convo_AdPerformed;
            newConvo.AdPossiblyPerformed += newConvo_AdPossiblyPerformed;
            newConvo.CaptchaPerformed += newConvo_CaptchaPerformed;
            newConvo.DecaptchaStart += newConvo_DecaptchaStart;
            newConvo.ConvoStartFailed += newConvo_ConvoStartFailed;
            newConvo.DecaptchaServiceError += newConvo_DecaptchaServiceError;
            newConvo.ConnectionError += newConvo_ConnectionError;
            newConvo.ReplyTimeOut += newConvo_ReplyTimeOut;
            newConvo.TimeoutError += newConvo_TimeoutError;
            newConvo.PartnerDisconnected += newConvo_PartnerDisconnected;
            newConvo.InstantDisconnected += newConvo_InstantDisconnected;
            return newConvo; 
        }

        void newConvo_InstantDisconnected(OmegleConvo sender, string message)
        {
            Log("Instant Disconnect on " + GetPlatform(sender) + " Extra: " + message);
            stats.StatsLive.InstantDisconnects++;
        }

        void newConvo_PartnerDisconnected(OmegleConvo sender, string message)
        {
            Log("Stranger Disconnected on " + GetPlatform(sender) + " Extra: " + message);
            stats.StatsLive.StrangerDisconnects++;
        }

        void newConvo_TimeoutError(OmegleConvo sender)
        {
            Log("Timeout error on " + GetPlatform(sender));
            stats.StatsLive.TimeoutErrors++;
        }

        void newConvo_ReplyTimeOut(OmegleConvo sender)
        {
            Log("Reply timed out on " + GetPlatform(sender));
            stats.StatsLive.ReplyTimedOuts++;
        }

        void newConvo_ConnectionError(OmegleConvo sender)
        {
            RegisterFailedProxy(GetPlatform(sender));
            Log("Connection error on " + GetPlatform(sender));
            stats.StatsLive.ConnectionErrors++;
        }

        void newConvo_DecaptchaServiceError(OmegleConvo sender)
        {
            Log("Decaptcha service errored on " + GetPlatform(sender));
            stats.StatsLive.DecaptchaServiceErrors++;
        }

        void newConvo_ConvoStartFailed(OmegleConvo sender, bool kill)
        {
            RegisterFailedProxy(GetPlatform(sender), kill: kill);
            Log("ConvoStartFailed failed on " + GetPlatform(sender));
            stats.StatsLive.ConvoStartFailed++;
        }

        void newConvo_DecaptchaStart(OmegleConvo sender)
        {
            Log("Decaptcha started on " + GetPlatform(sender));
        }

        void RegisterFailedProxy(string proxy, bool clearRecord = false, bool kill = false)
        {
            if (clearRecord)
            {
                if (failedProxies.ContainsKey(proxy)) failedProxies.Remove(proxy);
            }
            else
            {
                if (!failedProxies.ContainsKey(proxy)) failedProxies.Add(proxy, 1);

                failedProxies[proxy]++;
            }

        }

        string GetPlatform(OmegleConvo convo)
        {
            if (convo.communication.proxyInfo != null)
                return convo.communication.proxyInfo.ToString();
            else
                return "localhost";
        }

        void newConvo_CaptchaPerformed(OmegleConvo sender, bool successful)
        {
            if (successful)
            {
                stats.StatsLive.CaptchasPerformed++;
                Log("Captcha performed on " + GetPlatform(sender));
            }
            else
            {
                stats.StatsLive.CaptchasFailed++;
                Log("Captcha failed on " + GetPlatform(sender));
            }
            
        }


        void newConvo_AdPossiblyPerformed(OmegleConvo sender, string extraInfo)
        {
            stats.StatsLive.AdsPossiblyPerformed++;
            Log("Ad probably performed on " + GetPlatform(sender) + " - " + extraInfo);
        }

        void convo_AdPerformed(OmegleConvo sender, bool successful)
        {
            if (successful)
            {
                RegisterFailedProxy(GetPlatform(sender), clearRecord: true);
                stats.StatsLive.AdsPerformed++;
                Log("Ad performed on " + GetPlatform(sender));
            }
            else
            {
                stats.StatsLive.AdsFailed++;
                Log("Ad failed on " + GetPlatform(sender));
            }
        }

        private void Log(string text)
        {
            logText += DateTime.Now.ToLongTimeString() + " - " + text + Environment.NewLine;
        }

        string buttonStartStart = "Start";
        string buttonStartStop = "Stop";

        private void buttonStart_Click(object sender, EventArgs e)
        {
            if (buttonStart.Text == buttonStartStart)
            {
                buttonStart.Enabled = false;
                captchaLoginWorked.RunWorkerAsync();
            }
            else
            {
                started = false;
                buttonStart.Text = buttonStartStart;
                Log("Stopped");
            }
        }

        private void buttonSave_Click(object sender, EventArgs e)
        {
            Settings.Default.Proxies = textBoxProxies.Text;
            Settings.Default.ConversationsToRunAtOnce = (int)numericUpDownRunAtOnce.Value;
            Settings.Default.CaptchaUsername = textBoxCatpchaUsername.Text;
            Settings.Default.CatpchaPassword = textBoxCaptchaPassword.Text;
            Settings.Default.FailThreshold = (int)numericUpDownFailThreshold.Value;
            Settings.Default.Save();

            SaveConversationParts();
        }

        private void buttonRemoveUnusable_Click(object sender, EventArgs e)
        {
            List<string> proxies = new List<string>(textBoxProxies.Lines);
            foreach (string unusableProxy in listBoxUnusable.Items)
            {
                if (failedProxies.ContainsKey(unusableProxy)) failedProxies.Remove(unusableProxy);
                foreach (string proxy in proxies)
                {
                    if (proxy == unusableProxy)
                    {
                        proxies.Remove(proxy);
                        break;
                    }
                }
            }
            textBoxProxies.Text = string.Join(Environment.NewLine, proxies.ToArray());
            listBoxUnusable.Items.Clear();
        }

        private void buttonConversationBuilder_Click(object sender, EventArgs e)
        {
            conversationBuilder.Show();
        }

        void SaveConversationParts()
        {
            ArrayList conversationInfos = new ArrayList();
            foreach (OmegleConversationBuilder.ConversationPartControl part in conversationParts)
            {
                conversationInfos.Add(part.ConversationInfo);
            }

            var fStream = new FileStream("conversation.xml", FileMode.Create, FileAccess.Write, FileShare.None);
            var xmlForamt = new XmlSerializer(typeof(ArrayList), new Type[] { typeof(OmegleConversationBuilder.ConversationPartControl.ConversationData) });
            xmlForamt.Serialize(fStream, conversationInfos);
            fStream.Close();
        }

        void LoadConversationParts()
        {
            FileStream fStream = null;
            try
            {
                fStream = new FileStream("conversation.xml", FileMode.Open, FileAccess.Read, FileShare.None);
                var xmlForamt = new XmlSerializer(typeof(ArrayList), new Type[] { typeof(OmegleConversationBuilder.ConversationPartControl.ConversationData) });
                ArrayList conversationInfos = xmlForamt.Deserialize(fStream) as ArrayList;
                conversationBuilder = new OmegleConversationBuilder.ConversationBuilder(conversationParts, conversationInfos);
            }
            catch
            {
                conversationBuilder = new OmegleConversationBuilder.ConversationBuilder(conversationParts, new ArrayList(new OmegleConversationBuilder.ConversationPartControl.ConversationData[] { new OmegleConversationBuilder.ConversationPartControl.ConversationData() }));
            }

            if (fStream != null) fStream.Close();
        }

        private void numericUpDownFailThreshold_ValueChanged(object sender, EventArgs e)
        {
            Settings.Default.FailThreshold = (int)numericUpDownFailThreshold.Value;
        }
    }

    public static class Extentions
    {
        public static void Shuffle<T>(this IList<T> list)
        {
            Random rng = new Random();
            int n = list.Count;
            while (n > 1)
            {
                n--;
                int k = rng.Next(n + 1);
                T value = list[k];
                list[k] = list[n];
                list[n] = value;
            }
        }
    }
}
